<?php

namespace Classes;

class Redis {
	/**
	 * @var \Classes\Redis
	 */
	private static $INSTANCE;
	private $opt = [
		'host'     => '127.0.0.1',
		'port'     => 6379,
		'password' => '',
		'index'    => 0,
		'prefix'   => ''
	];

	private $redis;

	/**
	 * @param  array{host:string,port:int,password:string,prefix:string,select:int}  $opt
	 *
	 * @throws \Exception
	 */
	private function __construct ($opt = []) {
		// 检测php环境
		if (!self::enable())
			throw new \Exception('未开启redis扩展');

		$this->opt = array_merge($this->opt, array_filter($opt));

		$this->redis = new \Redis();
		$this->redis->connecT($this->opt['host'], $this->opt['port']);
		$pass = strval($this->opt['password']);
		if ($pass !== '') $this->redis->auth($pass);

		$index = intval($this->opt['index']);
		if ($index > 0) self::$INSTANCE->redis->select($index);
	}

	public static function enable () {
		return extension_loaded('Redis');
	}

	/**
	 * redis 实例生成
	 *
	 * @param  array{host:string,port:int,password:string,prefix:string,select:int}  $opt
	 * @param  bool                                                                  $renew
	 *
	 * @return Redis
	 * @throws \Exception
	 */
	public static function instance ($opt = [], $renew = true) {
		try {
			if (!(self::$INSTANCE instanceof self) || $renew) {
				self::$INSTANCE = new self($opt);
			} else {
				$index = intval($opt['index']);
				if (self::$INSTANCE->opt['index'] !== $index)
					self::$INSTANCE->redis->select($index);

				self::$INSTANCE->opt = array_merge(self::$INSTANCE->opt, $opt);
			}

			return self::$INSTANCE;
		} catch (\Exception $e) {
			exit(jsonMsg(-100, 'Redis 连接失败:' . $e->getMessage()));
		}
	}

	/**
	 * @param  string  $prefix
	 * @param  int     $index
	 *
	 * @return \Classes\Redis
	 * @throws \Exception
	 */
	public static function prefix ($prefix, $index = 0) {
		return self::select($index, $prefix);
	}

	/**
	 * @param  int     $index
	 * @param  string  $prefix
	 *
	 * @return \Classes\Redis
	 * @throws \Exception
	 */
	public static function select ($index, $prefix = '') {
		return self::instance(['prefix' => $prefix, 'select' => $index], false);
	}

	/**
	 * 设置值  构建一个字符串
	 *
	 * @param  string|array-key  $key      KEY名称或键值对
	 * @param  string|int        $value    设置值或过期时间
	 * @param  int               $timeOut  时间  -1表示无过期时间
	 *
	 * @return boolean
	 * @throws \RedisException
	 */
	public function set ($key, $value, $timeOut = -1) {
		$key = $this->convertKey($key);
		if (is_array($key)) {
			$timeOut = $value;
			$retRes  = $this->redis->mset($key);
			if ($retRes && $timeOut > 0)
				foreach ($key as $k => $v)
					$this->redis->expire($k, $timeOut);

			return $retRes;
		}

		return $timeOut > 0 ? $this->redis->setex($key, $timeOut, $value) : $this->redis->set($key, $value);
	}

	/**
	 * 修改key过期时间
	 *
	 * @param  string  $key
	 * @param  int     $expiry
	 *
	 * @return $this
	 * @throws \RedisException
	 */
	public function expiry ($key, $expiry = -1) {
		$this->redis->expire($this->convertKey($key), $expiry);

		return $this;
	}

	/**
	 * 获取key剩余时间（秒）
	 *
	 * @param  string  $key
	 *
	 * @return bool|int
	 * @throws \RedisException
	 */
	public function ttl ($key) {
		return $this->redis->ttl($this->convertKey($key));
	}

	/**
	 * 获取key剩余时间（毫秒）
	 *
	 * @param  string  $key
	 *
	 * @return bool|int
	 * @throws \RedisException
	 */
	public function pttl ($key) {
		return $this->redis->pttl($this->convertKey($key));
	}

	/**
	 * 构建一个集合(无序集合)
	 *
	 * @param  string  $key    集合键名
	 * @param  string  $value  值
	 *
	 * @return bool|int
	 */
	public function sadd ($key, ...$value) {
		return $this->redis->sadd($this->convertKey($key), ...$value);
	}

	/**
	 * 构建一个集合(无序集合)
	 *
	 * @param  string  $key    集合键名
	 * @param  array   $value  值
	 *
	 * @return bool|int
	 * @throws \RedisException
	 */
	public function sadds ($key, $value) {
		return $this->redis->sAddArray($this->convertKey($key), $value);
	}

	/**
	 * 构建一个集合(有序集合)
	 *
	 * @param  string        $key    集合名称
	 * @param                $score
	 * @param  string|array  $value  值
	 * @param                $value1
	 *
	 * @return false|int
	 * @throws \RedisException
	 */
	public function zadd ($key, $score, $value, $value1) {
		return $this->redis->zadd($this->convertKey($key), $score, $value, $value1);
	}

	/**
	 * 取集合对应元素
	 *
	 * @param  string  $key  集合名字
	 *
	 * @return array
	 * @throws \RedisException
	 */
	public function gets ($key) {
		return $this->redis->smembers($key);
	}

	/**
	 * 构建一个列表(先进后去，类似栈)
	 *
	 * @param  string  $key    KEY名称
	 * @param  string  $value  值
	 *
	 * @return false|int
	 * @throws \RedisException
	 */
	public function lpush ($key, $value) {
		return $this->redis->LPUSH($this->convertKey($key), $value);
	}

	/**
	 * 构建一个列表(先进先去，类似队列)
	 *
	 * @param  string  $key    KEY名称
	 * @param  string  $value  值
	 *
	 * @return bool|int
	 * @throws \RedisException
	 */
	public function rpush ($key, $value) {
		return $this->redis->rpush($this->convertKey($key), $value);
	}

	/**
	 * 获取所有列表数据（从头到尾取）
	 *
	 * @param  string  $key   KEY名称
	 * @param  int     $head  开始
	 * @param  int     $tail  结束
	 *
	 * @return array
	 * @throws \RedisException
	 */
	public function lrange ($key, $head, $tail) {
		return $this->redis->lrange($this->convertKey($key), $head, $tail);
	}

	/**
	 * HASH类型
	 *
	 * @param  string  $key    表名字key
	 * @param  string  $hashKey
	 * @param  string  $value  值
	 *
	 * @return bool|int
	 * @throws \RedisException
	 */
	public function hset ($key, $hashKey, $value) {
		return $this->redis->hset($this->convertKey($key), $hashKey, $value);
	}

	/**
	 * @param  string  $key
	 * @param  string  $hashKey
	 *
	 * @return false|\Redis|string
	 * @throws \RedisException
	 */
	public function hget ($key, $hashKey) {
		return $this->redis->hget($this->convertKey($key), $hashKey);
	}

	/**
	 * 通过key获取数据
	 *
	 * @param  string|array  $key  KEY名称
	 *
	 * @return mixed
	 * @throws \RedisException
	 */
	public function get ($key) {
		$key = $this->convertKey($key);

		return is_array($key) ? $this->redis->mget($key) : $this->redis->get($key);
	}

	/**
	 * 获取所有匹配的key名，不是值
	 *
	 * @param  string  $rule  dos表达式规则
	 *
	 * @return array
	 * @throws \RedisException
	 */
	public function keys ($rule = '*') {
		return $this->redis->keys($this->convertKey($rule));
	}

	/**
	 * 删除key数据
	 *
	 * @param  string|array  $key
	 *
	 * @return int
	 * @throws \RedisException
	 */
	public function del ($key) {
		return $this->redis->del($this->convertKey($key));
	}

	/**
	 * 数据自增
	 *
	 * @param  string  $key  KEY名称
	 *
	 * @return int
	 * @throws \RedisException
	 */
	public function increment ($key) {
		return $this->redis->incr($this->convertKey($key));
	}

	/**
	 * 数据自减
	 *
	 * @param  string  $key  KEY名称
	 *
	 * @return int
	 * @throws \RedisException
	 */
	public function decrement ($key) {
		return $this->redis->decr($this->convertKey($key));
	}

	/**
	 * 判断key是否存在
	 *
	 * @param  string  $key  KEY名称
	 *
	 * @return boolean
	 * @throws \RedisException
	 */
	public function exists ($key) {
		return $this->redis->exists($this->convertKey($key));
	}

	/**
	 * 重命名key
	 *
	 * @param  string  $key      原key
	 * @param  string  $new_key  新key
	 *
	 * @return bool|\Redis
	 * @throws \RedisException
	 */
	public function rename ($key, $new_key) {
		return $this->redis->rename($this->convertKey($key), $this->convertKey($new_key));
	}

	/**
	 * 重命名- 当且仅当$new_key不存在时，将key改为$new_key ，当$new_key存在时候会报错
	 *  和 rename不一样，它是直接更新（存在的值也会直接更新）
	 *
	 * @param  string  $key      原key
	 * @param  string  $new_key  新key
	 *
	 * @return bool
	 * @throws \RedisException
	 */
	public function renameNx ($key, $new_key) {
		return $this->redis->renameNx($this->convertKey($key), $this->convertKey($new_key));
	}

	/**
	 * 获取KEY存储的值类型
	 * none(key不存在) int(0)  string(字符串) int(1)   list(列表) int(3)  set(集合) int(2)   zset(有序集) int(4)    hash(哈希表) int(5)
	 *
	 * @param  string  $key  KEY名称
	 *
	 * @return int
	 * @throws \RedisException
	 */
	public function type ($key) {
		return $this->redis->type($this->convertKey($key));
	}

	/**
	 * 清空数据
	 *
	 * @return bool|\Redis
	 * @throws \RedisException
	 */
	public function flushAll () {
		return $this->redis->flushAll();
	}

	public function __clone () {
		trigger_error('Clone is not allow!', E_USER_ERROR);
	}

	/**
	 * 返回redis对象
	 * redis有非常多的操作方法，这里只封装了一部分
	 * 拿着这个对象就可以直接调用redis自身方法
	 * eg:$redis->redisOtherMethods()->keys('*a*')   keys方法没封
	 */
	public function redisOtherMethods () {
		return $this->redis;
	}

	/**
	 * 根据key所设定的前缀转化为真实key
	 *
	 * @param  array|string  $key
	 *
	 * @return array|string
	 */
	private function convertKey ($key) {
		if ($this->opt['prefix'] === '')
			return $key;

		if (!is_array($key))
			return $this->opt['prefix'] . $key;

		$keys  = [];
		$assoc = array_keys($key) === range(0, count($key) - 1);
		foreach ($key as $k => $v) {
			if ($assoc)
				$keys[] = $this->opt['prefix'] . $v;
			else
				$keys[$this->opt['prefix'] . $k] = $v;
		}

		return $keys;
	}
}